In your final repo, there should be an R markdown file that organizes all computational steps for evaluating your proposed Facial Expression Recognition framework.

This file is currently a template for running evaluation experiments. You should update it according to your codes but following precisely the same structure.

if(!require("EBImage")){
  install.packages("BiocManager")
  BiocManager::install("EBImage")
}
if(!require("R.matlab")){
  install.packages("R.matlab")
}
if(!require("readxl")){
  install.packages("readxl")
}

if(!require("dplyr")){
  install.packages("dplyr")
}
if(!require("readxl")){
  install.packages("readxl")
}

if(!require("ggplot2")){
  install.packages("ggplot2")
}

if(!require("caret")){
  install.packages("caret")
}

if(!require("glmnet")){
  install.packages("glmnet")
}

if(!require("WeightedROC")){
  install.packages("WeightedROC")
}

if(!require("gbm")){
  install.packages("gbm")
}

# Install Miniconda (https://docs.conda.io/en/latest/miniconda.html)
if(!require("keras")){
  install.packages("keras")
}

if(!require("tensorflow")){
  install.packages("tensorflow")
  install_tensorflow()
}

use_condaenv("r-tensorflow")
library(keras)
library(tensorflow)

library(R.matlab)
library(readxl)
library(dplyr)
library(EBImage)
library(ggplot2)
library(caret)
library(glmnet)
library(WeightedROC)
library(gbm)

Step 0 set work directories

set.seed(2020)
# setwd("~/Project3-FacialEmotionRecognition/doc")
# here replace it with your own path or manually set it in RStudio to where this rmd file is located. 
# use relative path for reproducibility

Provide directories for training images. Training images and Training fiducial points will be in different subfolders.

train_dir <- "../data/train_set/" # This will be modified for different data sets.
train_image_dir <- paste(train_dir, "images/", sep="")
train_pt_dir <- paste(train_dir,  "points/", sep="")
train_label_path <- paste(train_dir, "label.csv", sep="") 

Step 1: set up controls for evaluation experiments.

In this chunk, we have a set of controls for the evaluation experiments.

# run.cv <- TRUE # run cross-validation on the training set
# sample.reweight <- TRUE # run sample reweighting in model training
# K <- 5  # number of CV folds
# run.feature.train <- TRUE # process features for training set
# run.test <- TRUE # run evaluation on an independent test set
# run.feature.test <- TRUE # process features for test set

sample.reweight <- TRUE # run sample reweighting in model training
K <- 5  # number of CV folds
run.feature.train <- TRUE # process features for training set
run.feature.test <- TRUE # process features for test set

run.cv_gbm <- TRUE # run GBM cross-validation on the training set
run.test_gbm <- TRUE # run GBM evaluation on an independent test set

run.cv_dnn <- TRUE # run DNN cross-validation on the training set
run.test_dnn <- TRUE # run DNN evaluation on an independent test set

Using cross-validation or independent test set evaluation, we compare the performance of models with different specifications. In this part, we tune parameter n.trees and shrinkage for GBM.

# GBM parameters
n.trees  <- c(500, 100, 1500)
shrinkage <- c(0.01, 0.05, 0.1)

Step 2: import data and train-test split

#train-test split
info <- read.csv(train_label_path)
n <- nrow(info)
n_train <- round(n*(4/5), 0)
train_idx <- sample(info$Index, n_train, replace = F)
test_idx <- setdiff(info$Index, train_idx)

If you choose to extract features from images, such as using Gabor filter, R memory will exhaust all images are read together. The solution is to repeat reading a smaller batch(e.g 100) and process them.

n_files <- length(list.files(train_image_dir))

image_list <- list()
for(i in 1:100){
   image_list[[i]] <- readImage(paste0(train_image_dir, sprintf("%04d", i), ".jpg"))
}

Fiducial points are stored in matlab format. In this step, we read them and store them in a list.

#function to read fiducial points
#input: index
#output: matrix of fiducial points corresponding to the index
readMat.matrix <- function(index){
     return(round(readMat(paste0(train_pt_dir, sprintf("%04d", index), ".mat"))[[1]],0))
}

#load fiducial points
fiducial_pt_list <- lapply(1:n_files, readMat.matrix)
strings not representable in native encoding will be translated to UTF-8
save(fiducial_pt_list, file="../output/fiducial_pt_list.RData")

Step 3: construct features and responses

Figure1

feature.R should be the wrapper for all your feature engineering functions and options. The function feature( ) should have options that correspond to different scenarios for your project and produces an R object that contains features and responses that are required by all the models you are going to evaluate later.

source("../lib/feature.R")
tm_feature_train <- NA
if(run.feature.train){
  tm_feature_train <- system.time(dat_train <- feature(fiducial_pt_list, train_idx))
  save(dat_train, file="../output/feature_train.RData")
}else{
  load(file="../output/feature_train.RData")
}

tm_feature_test <- NA
if(run.feature.test){
  tm_feature_test <- system.time(dat_test <- feature(fiducial_pt_list, test_idx))
  save(dat_test, file="../output/feature_test.RData")
}else{
  load(file="../output/feature_test.RData")
}

GBM

Step 4: Train a classification model with training features and responses

Call the train model and test model from library.

train.R and test.R should be wrappers for all your model training steps and your classification/prediction steps.

source("../lib/train_gbm.R") 
source("../lib/test_gbm.R")

Model selection with cross-validation

  • Do model selection by choosing among different values of training model parameters.
source("../lib/cross_validation_gbm.R")
feature_train = as.matrix(dat_train[, -6007])
label_train = as.integer(dat_train$label)
if(run.cv_gbm){
  res_cv_gbm <- matrix(0, nrow = length(n.trees) * length(shrinkage), ncol = 6)
  count = 0
  for(i in 1:length(n.trees)){
    for(j in 1:length(shrinkage)){
      count = count + 1
      cat("n.trees =", n.trees[i], "\n")
      cat("shrinkage =", shrinkage[j], "\n")
      res_cv <- cv.function_gbm(features = feature_train, labels = label_train, K,
                              n.trees[i], shrinkage[j], reweight = sample.reweight)
      
      res_cv_gbm[count,] <- c(n.trees[i], shrinkage[j], res_cv[1], res_cv[2], res_cv[3], res_cv[4])
    }
  }
  
  colnames(res_cv_gbm) <- c("n.trees","shrinkage","mean_error", "sd_error", "mean_AUC", "sd_AUC")
  save(res_cv_gbm, file="../output/res_cv_gbm.RData")
}else{
  load("../output/res_cv_gbm.RData")
}
n.trees = 500 
shrinkage = 0.01 
n.trees = 500 
shrinkage = 0.05 
n.trees = 500 
shrinkage = 0.1 
n.trees = 100 
shrinkage = 0.01 
n.trees = 100 
shrinkage = 0.05 
n.trees = 100 
shrinkage = 0.1 
n.trees = 1500 
shrinkage = 0.01 
n.trees = 1500 
shrinkage = 0.05 
n.trees = 1500 
shrinkage = 0.1 

Visualize cross-validation results.

res_cv_gbm <- as.data.frame(res_cv_gbm) 

if(run.cv_gbm){
  p1 <- res_cv_gbm %>% 
    ggplot(aes(x = n.trees, y = mean_error,
               ymin = mean_error - sd_error, ymax = mean_error + sd_error)) + 
    geom_crossbar() +
    facet_wrap(~shrinkage)+
    theme(axis.text.x = element_text(angle = 90, hjust = 1))
  
  p2 <- res_cv_gbm %>% 
    ggplot(aes(x = n.trees, y = mean_AUC,
               ymin = mean_AUC - sd_AUC, ymax = mean_AUC + sd_AUC)) + 
    geom_crossbar() +
    facet_wrap(~shrinkage)+
    theme(axis.text.x = element_text(angle = 90, hjust = 1))
  
  print(p1)
  print(p2)
}

  • Choose the “best” parameter value
# par_n.trees_best <- as.numeric(res_cv_gbm[which.min(res_cv_gbm$mean_error), 1])
# par_shrinkage_best <- as.numeric(res_cv_gbm[which.min(res_cv_gbm$mean_error), 2])

par_n.trees_best <- 500
par_shrinkage_best <- 0.05
  • Train the model with the entire training set using the selected model (model parameter) via cross-validation.
# training weights
weight_train <- rep(NA, length(label_train))
for (v in unique(label_train)){
  weight_train[label_train == v] = 0.5 * length(label_train) / length(label_train[label_train == v])
}
if (sample.reweight){
  tm_train <- system.time(fit_train <- train_gbm(feature_train, label_train, w = weight_train, par_n.trees_best, par_shrinkage_best))
} else {
  tm_train <- system.time(fit_train <- train_gbm(feature_train, label_train, w = NULL, par_n.trees_best, par_shrinkage_best))
}
save(fit_train, file="../output/fit_train_gbm.RData")

Step 5: Run test on test images

tm_test = NA
feature_test <- as.matrix(dat_test[, -6007])
if(run.test_gbm){
  load(file="../output/fit_train_gbm.RData")
  tm_test <- system.time({prob_pred <- test_gbm(fit_train, feature_test, par_n.trees_best, pred.type = 'response');})
}
label_test <- as.integer(dat_test$label)

# DELETE
save(feature_test, file="../output/feature_test_tmp_only.RData")
Error in save(feature_test, file = "../output/feature_test_tmp_only.RData") : 
  目标对象‘feature_test’不存在

Summarize Running Time

Prediction performance matters, so does the running times for constructing features and for training the model, especially when the computation resource is limited.

cat("Time for constructing training features=", tm_feature_train[1], "s \n")
Time for constructing training features= 0.64 s 
cat("Time for constructing testing features=", tm_feature_test[1], "s \n")
Time for constructing testing features= 0.12 s 
cat("Time for training model=", tm_train[1], "s \n") 
Time for training model= 80.55 s 
cat("Time for testing model=", tm_test[1], "s \n")
Time for testing model= 0.12 s 

DNN

Step 4: Train a classification model with training features and responses

Call the train model and test model from library.

train.R and test.R should be wrappers for all your model training steps and your classification/prediction steps.

source("../lib/train_dnn.R") 
source("../lib/test_dnn.R")

Model selection with cross-validation

  • Do model selection by choosing among different values of training model parameters.
source("../lib/cross_validation_dnn.R")
feature_train = as.matrix(dat_train[, -6007])
label_train = as.integer(dat_train$label)

# if(run.cv_dnn){
#   res_cv_dnn <- matrix(0, 1, ncol = 4)
#   
#   res_cv <- cv.function_dnn(features = feature_train, labels = label_train, K,
#                           reweight = sample.reweight)
#   
#   res_cv_dnn[1,] <- c(res_cv[1], res_cv[2], res_cv[3], res_cv[4])
#   
#   colnames(res_cv_dnn) <- c("mean_error", "sd_error", "mean_AUC", "sd_AUC")
#   save(res_cv_dnn, file="../output/res_cv_dnn.RData")
# }else{
#   load("../output/res_cv_dnn.RData")
# }

Visualize cross-validation results.

# res_cv_dnn <- as.data.frame(res_cv_dnn) 
# 
# if(run.cv_dnn){
#   p1 <- res_cv_dnn %>% 
#     ggplot(aes(x = "DNN", y = mean_error,
#                ymin = mean_error - sd_error, ymax = mean_error + sd_error)) + 
#     geom_crossbar() +
#     theme(axis.text.x = element_text())
#   
#   p2 <- res_cv_dnn %>% 
#     ggplot(aes(x = "DNN", y = mean_AUC,
#                ymin = mean_AUC - sd_AUC, ymax = mean_AUC + sd_AUC)) + 
#     geom_crossbar() +
#     theme(axis.text.x = element_text())
#   
#   print(p1)
#   print(p2)
# }
  • Choose the “best” parameter value
# par_n.trees_best <- as.numeric(res_cv_gbm[which.min(res_cv_gbm$mean_error), 1])
# par_shrinkage_best <- as.numeric(res_cv_gbm[which.min(res_cv_gbm$mean_error), 2])
  • Train the model with the entire training set using the selected model (model parameter) via cross-validation.
source("../lib/train_dnn.R") 
source("../lib/test_dnn.R")

feature_train = as.matrix(dat_train[, -6007])
label_train = as.integer(dat_train$label)
feature_test <- as.matrix(dat_test[, -6007])
label_test <- as.integer(dat_test$label)
label_test <- ifelse(label_test == 2, 0, 1)
weight_test <- rep(NA, length(label_test))

# training weights
weight_train <- rep(NA, length(label_train))
for (v in unique(label_train)){
  weight_train[label_train == v] = 0.5 * length(label_train) / length(label_train[label_train == v])
}

for (v in unique(label_test)){
  weight_test[label_test == v] = 0.5 * length(label_test) / length(label_test[label_test == v])
}

# validation_split <- 1 / 5
# validation_split <- 0
tm_train <- NA
if (sample.reweight){
  tm_train <- system.time(fit_train <- train_dnn(feature_train, label_train, w = weight_train, feature_test, label_test, weight_test))
} else {
  tm_train <- system.time(fit_train <- train_dnn(feature_train, label_train, w = NULL, feature_test, label_test, weight_test = NULL))
}

  1/480 [..............................] - ETA: 0s - loss: 0.7364 - accuracy: 0.6000
  4/480 [..............................] - ETA: 6s - loss: 0.4994 - accuracy: 0.7000
  7/480 [..............................] - ETA: 7s - loss: 1.0844 - accuracy: 0.7429
 10/480 [..............................] - ETA: 7s - loss: 1.7769 - accuracy: 0.7200
 13/480 [..............................] - ETA: 7s - loss: 1.7024 - accuracy: 0.7231
 16/480 [>.............................] - ETA: 7s - loss: 1.5212 - accuracy: 0.7000
 19/480 [>.............................] - ETA: 7s - loss: 1.4686 - accuracy: 0.6632
 22/480 [>.............................] - ETA: 7s - loss: 1.3905 - accuracy: 0.6455
 25/480 [>.............................] - ETA: 7s - loss: 1.3442 - accuracy: 0.6080
 28/480 [>.............................] - ETA: 7s - loss: 1.3168 - accuracy: 0.5786
 31/480 [>.............................] - ETA: 7s - loss: 1.2960 - accuracy: 0.5548
 34/480 [=>............................] - ETA: 7s - loss: 1.3006 - accuracy: 0.5471
 37/480 [=>............................] - ETA: 7s - loss: 1.3099 - accuracy: 0.5459
 40/480 [=>............................] - ETA: 7s - loss: 1.2814 - accuracy: 0.5450
 43/480 [=>............................] - ETA: 7s - loss: 1.2587 - accuracy: 0.5395
 46/480 [=>............................] - ETA: 7s - loss: 1.2075 - accuracy: 0.5565
 49/480 [==>...........................] - ETA: 7s - loss: 1.1759 - accuracy: 0.5633
 52/480 [==>...........................] - ETA: 7s - loss: 1.1447 - accuracy: 0.5538
 55/480 [==>...........................] - ETA: 7s - loss: 1.1477 - accuracy: 0.5491
 58/480 [==>...........................] - ETA: 7s - loss: 1.1282 - accuracy: 0.5483
 61/480 [==>...........................] - ETA: 7s - loss: 1.0982 - accuracy: 0.5508
 64/480 [===>..........................] - ETA: 7s - loss: 1.0816 - accuracy: 0.5594
 67/480 [===>..........................] - ETA: 7s - loss: 1.0922 - accuracy: 0.5552
 70/480 [===>..........................] - ETA: 6s - loss: 1.1125 - accuracy: 0.5543
 73/480 [===>..........................] - ETA: 6s - loss: 1.1091 - accuracy: 0.5479
 76/480 [===>..........................] - ETA: 6s - loss: 1.0961 - accuracy: 0.5447
 79/480 [===>..........................] - ETA: 6s - loss: 1.0903 - accuracy: 0.5291
 82/480 [====>.........................] - ETA: 6s - loss: 1.0778 - accuracy: 0.5293
 85/480 [====>.........................] - ETA: 6s - loss: 1.0688 - accuracy: 0.5200
 88/480 [====>.........................] - ETA: 6s - loss: 1.0699 - accuracy: 0.5182
 91/480 [====>.........................] - ETA: 6s - loss: 1.0587 - accuracy: 0.5209
 94/480 [====>.........................] - ETA: 6s - loss: 1.0887 - accuracy: 0.5277
 97/480 [=====>........................] - ETA: 6s - loss: 1.0596 - accuracy: 0.5402
100/480 [=====>........................] - ETA: 6s - loss: 1.0499 - accuracy: 0.5480
103/480 [=====>........................] - ETA: 6s - loss: 1.0461 - accuracy: 0.5515
106/480 [=====>........................] - ETA: 6s - loss: 1.0546 - accuracy: 0.5547
109/480 [=====>........................] - ETA: 6s - loss: 1.0432 - accuracy: 0.5596
112/480 [======>.......................] - ETA: 6s - loss: 1.0295 - accuracy: 0.5589
115/480 [======>.......................] - ETA: 6s - loss: 1.0226 - accuracy: 0.5600
118/480 [======>.......................] - ETA: 6s - loss: 1.0146 - accuracy: 0.5610
121/480 [======>.......................] - ETA: 6s - loss: 1.0088 - accuracy: 0.5570
124/480 [======>.......................] - ETA: 6s - loss: 1.0161 - accuracy: 0.5532
127/480 [======>.......................] - ETA: 6s - loss: 1.0262 - accuracy: 0.5512
130/480 [=======>......................] - ETA: 6s - loss: 1.0196 - accuracy: 0.5523
133/480 [=======>......................] - ETA: 5s - loss: 1.0163 - accuracy: 0.5489
136/480 [=======>......................] - ETA: 5s - loss: 1.0131 - accuracy: 0.5441
139/480 [=======>......................] - ETA: 5s - loss: 1.0070 - accuracy: 0.5439
142/480 [=======>......................] - ETA: 5s - loss: 1.0020 - accuracy: 0.5451
145/480 [========>.....................] - ETA: 5s - loss: 0.9966 - accuracy: 0.5434
148/480 [========>.....................] - ETA: 5s - loss: 0.9970 - accuracy: 0.5405
151/480 [========>.....................] - ETA: 5s - loss: 0.9939 - accuracy: 0.5404
154/480 [========>.....................] - ETA: 5s - loss: 0.9915 - accuracy: 0.5403
157/480 [========>.....................] - ETA: 5s - loss: 0.9900 - accuracy: 0.5376
160/480 [=========>....................] - ETA: 5s - loss: 0.9872 - accuracy: 0.5337
163/480 [=========>....................] - ETA: 5s - loss: 0.9874 - accuracy: 0.5325
166/480 [=========>....................] - ETA: 5s - loss: 0.9820 - accuracy: 0.5265
169/480 [=========>....................] - ETA: 5s - loss: 0.9771 - accuracy: 0.5266
172/480 [=========>....................] - ETA: 5s - loss: 0.9768 - accuracy: 0.5244
175/480 [=========>....................] - ETA: 5s - loss: 0.9786 - accuracy: 0.5257
178/480 [==========>...................] - ETA: 5s - loss: 0.9735 - accuracy: 0.5281
181/480 [==========>...................] - ETA: 5s - loss: 0.9806 - accuracy: 0.5260
184/480 [==========>...................] - ETA: 5s - loss: 0.9748 - accuracy: 0.5261
187/480 [==========>...................] - ETA: 5s - loss: 0.9684 - accuracy: 0.5241
190/480 [==========>...................] - ETA: 5s - loss: 0.9684 - accuracy: 0.5242
193/480 [===========>..................] - ETA: 4s - loss: 0.9721 - accuracy: 0.5244
196/480 [===========>..................] - ETA: 4s - loss: 0.9735 - accuracy: 0.5245
199/480 [===========>..................] - ETA: 4s - loss: 0.9864 - accuracy: 0.5226
202/480 [===========>..................] - ETA: 4s - loss: 0.9795 - accuracy: 0.5228
205/480 [===========>..................] - ETA: 4s - loss: 0.9765 - accuracy: 0.5220
208/480 [============>.................] - ETA: 4s - loss: 0.9748 - accuracy: 0.5231
211/480 [============>.................] - ETA: 4s - loss: 0.9733 - accuracy: 0.5213
214/480 [============>.................] - ETA: 4s - loss: 0.9743 - accuracy: 0.5187
217/480 [============>.................] - ETA: 4s - loss: 0.9714 - accuracy: 0.5143
220/480 [============>.................] - ETA: 4s - loss: 0.9705 - accuracy: 0.5127
223/480 [============>.................] - ETA: 4s - loss: 0.9649 - accuracy: 0.5112
226/480 [=============>................] - ETA: 4s - loss: 0.9578 - accuracy: 0.5150
229/480 [=============>................] - ETA: 4s - loss: 0.9495 - accuracy: 0.5188
232/480 [=============>................] - ETA: 4s - loss: 0.9496 - accuracy: 0.5198
235/480 [=============>................] - ETA: 4s - loss: 0.9541 - accuracy: 0.5226
238/480 [=============>................] - ETA: 4s - loss: 0.9538 - accuracy: 0.5218
241/480 [==============>...............] - ETA: 4s - loss: 0.9494 - accuracy: 0.5212
244/480 [==============>...............] - ETA: 4s - loss: 0.9464 - accuracy: 0.5197
247/480 [==============>...............] - ETA: 4s - loss: 0.9438 - accuracy: 0.5206
250/480 [==============>...............] - ETA: 4s - loss: 0.9420 - accuracy: 0.5192
253/480 [==============>...............] - ETA: 3s - loss: 0.9373 - accuracy: 0.5194
256/480 [===============>..............] - ETA: 3s - loss: 0.9392 - accuracy: 0.5180
259/480 [===============>..............] - ETA: 3s - loss: 0.9357 - accuracy: 0.5166
262/480 [===============>..............] - ETA: 3s - loss: 0.9347 - accuracy: 0.5168
265/480 [===============>..............] - ETA: 3s - loss: 0.9290 - accuracy: 0.5177
268/480 [===============>..............] - ETA: 3s - loss: 0.9275 - accuracy: 0.5194
271/480 [===============>..............] - ETA: 3s - loss: 0.9254 - accuracy: 0.5188
274/480 [================>.............] - ETA: 3s - loss: 0.9216 - accuracy: 0.5212
277/480 [================>.............] - ETA: 3s - loss: 0.9181 - accuracy: 0.5213
280/480 [================>.............] - ETA: 3s - loss: 0.9167 - accuracy: 0.5229
283/480 [================>.............] - ETA: 3s - loss: 0.9152 - accuracy: 0.5265
286/480 [================>.............] - ETA: 3s - loss: 0.9175 - accuracy: 0.5259
289/480 [=================>............] - ETA: 3s - loss: 0.9161 - accuracy: 0.5260
292/480 [=================>............] - ETA: 3s - loss: 0.9154 - accuracy: 0.5240
295/480 [=================>............] - ETA: 3s - loss: 0.9142 - accuracy: 0.5220
298/480 [=================>............] - ETA: 3s - loss: 0.9116 - accuracy: 0.5221
301/480 [=================>............] - ETA: 3s - loss: 0.9088 - accuracy: 0.5229
304/480 [==================>...........] - ETA: 3s - loss: 0.9098 - accuracy: 0.5211
307/480 [==================>...........] - ETA: 3s - loss: 0.9076 - accuracy: 0.5225
310/480 [==================>...........] - ETA: 2s - loss: 0.9049 - accuracy: 0.5219
313/480 [==================>...........] - ETA: 2s - loss: 0.9054 - accuracy: 0.5208
316/480 [==================>...........] - ETA: 2s - loss: 0.9049 - accuracy: 0.5190
319/480 [==================>...........] - ETA: 2s - loss: 0.9013 - accuracy: 0.5185
322/480 [===================>..........] - ETA: 2s - loss: 0.8990 - accuracy: 0.5174
325/480 [===================>..........] - ETA: 2s - loss: 0.8953 - accuracy: 0.5182
328/480 [===================>..........] - ETA: 2s - loss: 0.8963 - accuracy: 0.5171
331/480 [===================>..........] - ETA: 2s - loss: 0.8968 - accuracy: 0.5196
334/480 [===================>..........] - ETA: 2s - loss: 0.8952 - accuracy: 0.5210
337/480 [====================>.........] - ETA: 2s - loss: 0.8934 - accuracy: 0.5175
340/480 [====================>.........] - ETA: 2s - loss: 0.8953 - accuracy: 0.5176
343/480 [====================>.........] - ETA: 2s - loss: 0.8953 - accuracy: 0.5155
346/480 [====================>.........] - ETA: 2s - loss: 0.8929 - accuracy: 0.5173
349/480 [====================>.........] - ETA: 2s - loss: 0.8902 - accuracy: 0.5169
352/480 [=====================>........] - ETA: 2s - loss: 0.8903 - accuracy: 0.5165
355/480 [=====================>........] - ETA: 2s - loss: 0.8897 - accuracy: 0.5161
358/480 [=====================>........] - ETA: 2s - loss: 0.8872 - accuracy: 0.5173
361/480 [=====================>........] - ETA: 2s - loss: 0.8861 - accuracy: 0.5163
364/480 [=====================>........] - ETA: 2s - loss: 0.8847 - accuracy: 0.5165
367/480 [=====================>........] - ETA: 1s - loss: 0.8824 - accuracy: 0.5166
370/480 [======================>.......] - ETA: 1s - loss: 0.8810 - accuracy: 0.5178
373/480 [======================>.......] - ETA: 1s - loss: 0.8825 - accuracy: 0.5169
376/480 [======================>.......] - ETA: 1s - loss: 0.8823 - accuracy: 0.5170
379/480 [======================>.......] - ETA: 1s - loss: 0.8823 - accuracy: 0.5156
382/480 [======================>.......] - ETA: 1s - loss: 0.8823 - accuracy: 0.5136
385/480 [=======================>......] - ETA: 1s - loss: 0.8809 - accuracy: 0.5138
388/480 [=======================>......] - ETA: 1s - loss: 0.8792 - accuracy: 0.5124
391/480 [=======================>......] - ETA: 1s - loss: 0.8768 - accuracy: 0.5125
394/480 [=======================>......] - ETA: 1s - loss: 0.8748 - accuracy: 0.5127
397/480 [=======================>......] - ETA: 1s - loss: 0.8753 - accuracy: 0.5134
400/480 [========================>.....] - ETA: 1s - loss: 0.8728 - accuracy: 0.5155
403/480 [========================>.....] - ETA: 1s - loss: 0.8732 - accuracy: 0.5161
406/480 [========================>.....] - ETA: 1s - loss: 0.8722 - accuracy: 0.5158
409/480 [========================>.....] - ETA: 1s - loss: 0.8696 - accuracy: 0.5169
412/480 [========================>.....] - ETA: 1s - loss: 0.8705 - accuracy: 0.5155
415/480 [========================>.....] - ETA: 1s - loss: 0.8686 - accuracy: 0.5137
418/480 [=========================>....] - ETA: 1s - loss: 0.8676 - accuracy: 0.5124
421/480 [=========================>....] - ETA: 1s - loss: 0.8664 - accuracy: 0.5116
424/480 [=========================>....] - ETA: 0s - loss: 0.8652 - accuracy: 0.5104
427/480 [=========================>....] - ETA: 0s - loss: 0.8639 - accuracy: 0.5101
430/480 [=========================>....] - ETA: 0s - loss: 0.8662 - accuracy: 0.5088
433/480 [==========================>...] - ETA: 0s - loss: 0.8638 - accuracy: 0.5099
436/480 [==========================>...] - ETA: 0s - loss: 0.8638 - accuracy: 0.5110
439/480 [==========================>...] - ETA: 0s - loss: 0.8613 - accuracy: 0.5112
442/480 [==========================>...] - ETA: 0s - loss: 0.8580 - accuracy: 0.5118
445/480 [==========================>...] - ETA: 0s - loss: 0.8626 - accuracy: 0.5119
448/480 [===========================>..] - ETA: 0s - loss: 0.8603 - accuracy: 0.5134
451/480 [===========================>..] - ETA: 0s - loss: 0.8627 - accuracy: 0.5122
454/480 [===========================>..] - ETA: 0s - loss: 0.8627 - accuracy: 0.5119
457/480 [===========================>..] - ETA: 0s - loss: 0.8618 - accuracy: 0.5120
460/480 [===========================>..] - ETA: 0s - loss: 0.8614 - accuracy: 0.5117
463/480 [===========================>..] - ETA: 0s - loss: 0.8603 - accuracy: 0.5123
466/480 [============================>.] - ETA: 0s - loss: 0.8584 - accuracy: 0.5112
469/480 [============================>.] - ETA: 0s - loss: 0.8576 - accuracy: 0.5113
472/480 [============================>.] - ETA: 0s - loss: 0.8566 - accuracy: 0.5106
475/480 [============================>.] - ETA: 0s - loss: 0.8540 - accuracy: 0.5107
478/480 [============================>.] - ETA: 0s - loss: 0.8566 - accuracy: 0.5113
480/480 [==============================] - 8s 18ms/step - loss: 0.8548 - accuracy: 0.5121

480/480 [==============================] - 9s 18ms/step - loss: 0.8548 - accuracy: 0.5121 - val_loss: 0.7072 - val_accuracy: 0.6617
cat("time--------------------------------", tm_train[c(1,2,3)])
time-------------------------------- 52.8 12.32 11.83
save_model_hdf5(fit_train, "../output/fit_train_dnn.h5")

Step 5: Run test on test images

tm_test = NA
feature_test <- as.matrix(dat_test[, -6007])
if(run.test_dnn){
  fit_train <- load_model_hdf5("../output/fit_train_dnn.h5")
  tm_test <- system.time({label_pred <- test_dnn(fit_train, feature_test, type = "predict_classes");
   prob_pred <- test_dnn(fit_train, feature_test, type = "predict_proba")})
}
## reweight the test data to represent a balanced label distribution
label_test <- as.integer(dat_test$label)

weight_test <- rep(NA, length(label_test))
for (v in unique(label_test)){
  weight_test[label_test == v] = 0.5 * length(label_test) / length(label_test[label_test == v])
}

label_test <- ifelse(label_test == 2, 0, 1)

accu <- sum(weight_test * (label_pred == label_test)) / sum(weight_test)

# prob_pred <- apply(prob_pred, 1, max)
head(label_pred)
[1] 1 0 1 1 1 1
head(prob_pred)
             [,1]      [,2]
[1,] 6.481969e-06 0.9999936
[2,] 5.308085e-01 0.4691916
[3,] 3.749197e-06 0.9999963
[4,] 8.148937e-33 1.0000000
[5,] 4.023731e-01 0.5976269
[6,] 5.316924e-02 0.9468307
prob_pred <- prob_pred[, 2]
head(prob_pred)
[1] 0.9999936 0.4691916 0.9999963 1.0000000 0.5976269 0.9468307
tpr.fpr <- WeightedROC(prob_pred, label_test, weight_test)
auc <- WeightedAUC(tpr.fpr)


cat("The accuracy of model:", "DNN", "is", accu*100, "%.\n")
The accuracy of model: DNN is 74.04045 %.
cat("The AUC of model:", "DNN", "is", auc, ".\n")
The AUC of model: DNN is 0.8018793 .

Summarize Running Time

Prediction performance matters, so does the running times for constructing features and for training the model, especially when the computation resource is limited.

cat("Time for constructing training features=", tm_feature_train[1], "s \n")
Time for constructing training features= 0.6 s 
cat("Time for constructing testing features=", tm_feature_test[1], "s \n")
Time for constructing testing features= 0.13 s 
cat("Time for training model=", tm_train[1], "s \n") 
Time for training model= 5331.72 s 
cat("Time for testing model=", tm_test[1], "s \n")
Time for testing model= 1.19 s 

###Reference - Du, S., Tao, Y., & Martinez, A. M. (2014). Compound facial expressions of emotion. Proceedings of the National Academy of Sciences, 111(15), E1454-E1462.

LS0tDQp0aXRsZTogIk1haW4iDQphdXRob3I6ICJDaGVuZ2xpYW5nIFRhbmcsIFl1amllIFdhbmcsIERpYW5lIEx1LCBUaWFuIFpoZW5nIg0Kb3V0cHV0Og0KICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQNCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KLS0tDQoNCkluIHlvdXIgZmluYWwgcmVwbywgdGhlcmUgc2hvdWxkIGJlIGFuIFIgbWFya2Rvd24gZmlsZSB0aGF0IG9yZ2FuaXplcyAqKmFsbCBjb21wdXRhdGlvbmFsIHN0ZXBzKiogZm9yIGV2YWx1YXRpbmcgeW91ciBwcm9wb3NlZCBGYWNpYWwgRXhwcmVzc2lvbiBSZWNvZ25pdGlvbiBmcmFtZXdvcmsuIA0KDQpUaGlzIGZpbGUgaXMgY3VycmVudGx5IGEgdGVtcGxhdGUgZm9yIHJ1bm5pbmcgZXZhbHVhdGlvbiBleHBlcmltZW50cy4gWW91IHNob3VsZCB1cGRhdGUgaXQgYWNjb3JkaW5nIHRvIHlvdXIgY29kZXMgYnV0IGZvbGxvd2luZyBwcmVjaXNlbHkgdGhlIHNhbWUgc3RydWN0dXJlLiANCg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCmlmKCFyZXF1aXJlKCJFQkltYWdlIikpew0KICBpbnN0YWxsLnBhY2thZ2VzKCJCaW9jTWFuYWdlciIpDQogIEJpb2NNYW5hZ2VyOjppbnN0YWxsKCJFQkltYWdlIikNCn0NCmlmKCFyZXF1aXJlKCJSLm1hdGxhYiIpKXsNCiAgaW5zdGFsbC5wYWNrYWdlcygiUi5tYXRsYWIiKQ0KfQ0KaWYoIXJlcXVpcmUoInJlYWR4bCIpKXsNCiAgaW5zdGFsbC5wYWNrYWdlcygicmVhZHhsIikNCn0NCg0KaWYoIXJlcXVpcmUoImRwbHlyIikpew0KICBpbnN0YWxsLnBhY2thZ2VzKCJkcGx5ciIpDQp9DQppZighcmVxdWlyZSgicmVhZHhsIikpew0KICBpbnN0YWxsLnBhY2thZ2VzKCJyZWFkeGwiKQ0KfQ0KDQppZighcmVxdWlyZSgiZ2dwbG90MiIpKXsNCiAgaW5zdGFsbC5wYWNrYWdlcygiZ2dwbG90MiIpDQp9DQoNCmlmKCFyZXF1aXJlKCJjYXJldCIpKXsNCiAgaW5zdGFsbC5wYWNrYWdlcygiY2FyZXQiKQ0KfQ0KDQppZighcmVxdWlyZSgiZ2xtbmV0Iikpew0KICBpbnN0YWxsLnBhY2thZ2VzKCJnbG1uZXQiKQ0KfQ0KDQppZighcmVxdWlyZSgiV2VpZ2h0ZWRST0MiKSl7DQogIGluc3RhbGwucGFja2FnZXMoIldlaWdodGVkUk9DIikNCn0NCg0KaWYoIXJlcXVpcmUoImdibSIpKXsNCiAgaW5zdGFsbC5wYWNrYWdlcygiZ2JtIikNCn0NCg0KIyBJbnN0YWxsIE1pbmljb25kYSAoaHR0cHM6Ly9kb2NzLmNvbmRhLmlvL2VuL2xhdGVzdC9taW5pY29uZGEuaHRtbCkNCmlmKCFyZXF1aXJlKCJrZXJhcyIpKXsNCiAgaW5zdGFsbC5wYWNrYWdlcygia2VyYXMiKQ0KfQ0KDQppZighcmVxdWlyZSgidGVuc29yZmxvdyIpKXsNCiAgaW5zdGFsbC5wYWNrYWdlcygidGVuc29yZmxvdyIpDQogIGluc3RhbGxfdGVuc29yZmxvdygpDQp9DQoNCnVzZV9jb25kYWVudigici10ZW5zb3JmbG93IikNCmxpYnJhcnkoa2VyYXMpDQpsaWJyYXJ5KHRlbnNvcmZsb3cpDQoNCmxpYnJhcnkoUi5tYXRsYWIpDQpsaWJyYXJ5KHJlYWR4bCkNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KEVCSW1hZ2UpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGNhcmV0KQ0KbGlicmFyeShnbG1uZXQpDQpsaWJyYXJ5KFdlaWdodGVkUk9DKQ0KbGlicmFyeShnYm0pDQpgYGANCg0KIyMjIFN0ZXAgMCBzZXQgd29yayBkaXJlY3Rvcmllcw0KYGBge3Igd2tkaXIsIGV2YWw9RkFMU0V9DQpzZXQuc2VlZCgyMDIwKQ0KIyBzZXR3ZCgifi9Qcm9qZWN0My1GYWNpYWxFbW90aW9uUmVjb2duaXRpb24vZG9jIikNCiMgaGVyZSByZXBsYWNlIGl0IHdpdGggeW91ciBvd24gcGF0aCBvciBtYW51YWxseSBzZXQgaXQgaW4gUlN0dWRpbyB0byB3aGVyZSB0aGlzIHJtZCBmaWxlIGlzIGxvY2F0ZWQuIA0KIyB1c2UgcmVsYXRpdmUgcGF0aCBmb3IgcmVwcm9kdWNpYmlsaXR5DQpgYGANCg0KUHJvdmlkZSBkaXJlY3RvcmllcyBmb3IgdHJhaW5pbmcgaW1hZ2VzLiBUcmFpbmluZyBpbWFnZXMgYW5kIFRyYWluaW5nIGZpZHVjaWFsIHBvaW50cyB3aWxsIGJlIGluIGRpZmZlcmVudCBzdWJmb2xkZXJzLiANCmBgYHtyfQ0KdHJhaW5fZGlyIDwtICIuLi9kYXRhL3RyYWluX3NldC8iICMgVGhpcyB3aWxsIGJlIG1vZGlmaWVkIGZvciBkaWZmZXJlbnQgZGF0YSBzZXRzLg0KdHJhaW5faW1hZ2VfZGlyIDwtIHBhc3RlKHRyYWluX2RpciwgImltYWdlcy8iLCBzZXA9IiIpDQp0cmFpbl9wdF9kaXIgPC0gcGFzdGUodHJhaW5fZGlyLCAgInBvaW50cy8iLCBzZXA9IiIpDQp0cmFpbl9sYWJlbF9wYXRoIDwtIHBhc3RlKHRyYWluX2RpciwgImxhYmVsLmNzdiIsIHNlcD0iIikgDQpgYGANCg0KIyMjIFN0ZXAgMTogc2V0IHVwIGNvbnRyb2xzIGZvciBldmFsdWF0aW9uIGV4cGVyaW1lbnRzLg0KDQpJbiB0aGlzIGNodW5rLCB3ZSBoYXZlIGEgc2V0IG9mIGNvbnRyb2xzIGZvciB0aGUgZXZhbHVhdGlvbiBleHBlcmltZW50cy4gDQoNCisgKFQvRikgY3Jvc3MtdmFsaWRhdGlvbiBvbiB0aGUgdHJhaW5pbmcgc2V0DQorIChUL0YpIHJld2VpZ2h0aW5nIHRoZSBzYW1wbGVzIGZvciB0cmFpbmluZyBzZXQgDQorIChudW1iZXIpIEssIHRoZSBudW1iZXIgb2YgQ1YgZm9sZHMNCisgKFQvRikgcHJvY2VzcyBmZWF0dXJlcyBmb3IgdHJhaW5pbmcgc2V0DQorIChUL0YpIHJ1biBldmFsdWF0aW9uIG9uIGFuIGluZGVwZW5kZW50IHRlc3Qgc2V0DQorIChUL0YpIHByb2Nlc3MgZmVhdHVyZXMgZm9yIHRlc3Qgc2V0DQoNCmBgYHtyIGV4cF9zZXR1cH0NCiMgcnVuLmN2IDwtIFRSVUUgIyBydW4gY3Jvc3MtdmFsaWRhdGlvbiBvbiB0aGUgdHJhaW5pbmcgc2V0DQojIHNhbXBsZS5yZXdlaWdodCA8LSBUUlVFICMgcnVuIHNhbXBsZSByZXdlaWdodGluZyBpbiBtb2RlbCB0cmFpbmluZw0KIyBLIDwtIDUgICMgbnVtYmVyIG9mIENWIGZvbGRzDQojIHJ1bi5mZWF0dXJlLnRyYWluIDwtIFRSVUUgIyBwcm9jZXNzIGZlYXR1cmVzIGZvciB0cmFpbmluZyBzZXQNCiMgcnVuLnRlc3QgPC0gVFJVRSAjIHJ1biBldmFsdWF0aW9uIG9uIGFuIGluZGVwZW5kZW50IHRlc3Qgc2V0DQojIHJ1bi5mZWF0dXJlLnRlc3QgPC0gVFJVRSAjIHByb2Nlc3MgZmVhdHVyZXMgZm9yIHRlc3Qgc2V0DQoNCnNhbXBsZS5yZXdlaWdodCA8LSBUUlVFICMgcnVuIHNhbXBsZSByZXdlaWdodGluZyBpbiBtb2RlbCB0cmFpbmluZw0KSyA8LSA1ICAjIG51bWJlciBvZiBDViBmb2xkcw0KcnVuLmZlYXR1cmUudHJhaW4gPC0gVFJVRSAjIHByb2Nlc3MgZmVhdHVyZXMgZm9yIHRyYWluaW5nIHNldA0KcnVuLmZlYXR1cmUudGVzdCA8LSBUUlVFICMgcHJvY2VzcyBmZWF0dXJlcyBmb3IgdGVzdCBzZXQNCg0KcnVuLmN2X2dibSA8LSBUUlVFICMgcnVuIEdCTSBjcm9zcy12YWxpZGF0aW9uIG9uIHRoZSB0cmFpbmluZyBzZXQNCnJ1bi50ZXN0X2dibSA8LSBUUlVFICMgcnVuIEdCTSBldmFsdWF0aW9uIG9uIGFuIGluZGVwZW5kZW50IHRlc3Qgc2V0DQoNCnJ1bi5jdl9kbm4gPC0gVFJVRSAjIHJ1biBETk4gY3Jvc3MtdmFsaWRhdGlvbiBvbiB0aGUgdHJhaW5pbmcgc2V0DQpydW4udGVzdF9kbm4gPC0gVFJVRSAjIHJ1biBETk4gZXZhbHVhdGlvbiBvbiBhbiBpbmRlcGVuZGVudCB0ZXN0IHNldA0KYGBgDQoNCjwhLS0gVXNpbmcgY3Jvc3MtdmFsaWRhdGlvbiBvciBpbmRlcGVuZGVudCB0ZXN0IHNldCBldmFsdWF0aW9uLCB3ZSBjb21wYXJlIHRoZSBwZXJmb3JtYW5jZSBvZiBtb2RlbHMgd2l0aCBkaWZmZXJlbnQgc3BlY2lmaWNhdGlvbnMuIEluIHRoaXMgU3RhcnRlciBDb2RlLCB3ZSB0dW5lIHBhcmFtZXRlciBsYW1iZGEgKHRoZSBhbW91bnQgb2Ygc2hyaW5rYWdlKSBmb3IgbG9naXN0aWMgcmVncmVzc2lvbiB3aXRoIExBU1NPIHBlbmFsdHkuIC0tPg0KDQpVc2luZyBjcm9zcy12YWxpZGF0aW9uIG9yIGluZGVwZW5kZW50IHRlc3Qgc2V0IGV2YWx1YXRpb24sIHdlIGNvbXBhcmUgdGhlIHBlcmZvcm1hbmNlIG9mIG1vZGVscyB3aXRoIGRpZmZlcmVudCBzcGVjaWZpY2F0aW9ucy4gSW4gdGhpcyBwYXJ0LCB3ZSB0dW5lIHBhcmFtZXRlciBuLnRyZWVzIGFuZCBzaHJpbmthZ2UgZm9yIEdCTS4NCg0KYGBge3IgbW9kZWxfc2V0dXB9DQojIEdCTSBwYXJhbWV0ZXJzDQpuLnRyZWVzICA8LSBjKDUwMCwgMTAwLCAxNTAwKQ0Kc2hyaW5rYWdlIDwtIGMoMC4wMSwgMC4wNSwgMC4xKQ0KYGBgDQoNCiMjIyBTdGVwIDI6IGltcG9ydCBkYXRhIGFuZCB0cmFpbi10ZXN0IHNwbGl0IA0KYGBge3J9DQojdHJhaW4tdGVzdCBzcGxpdA0KaW5mbyA8LSByZWFkLmNzdih0cmFpbl9sYWJlbF9wYXRoKQ0KbiA8LSBucm93KGluZm8pDQpuX3RyYWluIDwtIHJvdW5kKG4qKDQvNSksIDApDQp0cmFpbl9pZHggPC0gc2FtcGxlKGluZm8kSW5kZXgsIG5fdHJhaW4sIHJlcGxhY2UgPSBGKQ0KdGVzdF9pZHggPC0gc2V0ZGlmZihpbmZvJEluZGV4LCB0cmFpbl9pZHgpDQpgYGANCg0KSWYgeW91IGNob29zZSB0byBleHRyYWN0IGZlYXR1cmVzIGZyb20gaW1hZ2VzLCBzdWNoIGFzIHVzaW5nIEdhYm9yIGZpbHRlciwgUiBtZW1vcnkgd2lsbCBleGhhdXN0IGFsbCBpbWFnZXMgYXJlIHJlYWQgdG9nZXRoZXIuIFRoZSBzb2x1dGlvbiBpcyB0byByZXBlYXQgcmVhZGluZyBhIHNtYWxsZXIgYmF0Y2goZS5nIDEwMCkgYW5kIHByb2Nlc3MgdGhlbS4gDQpgYGB7cn0NCm5fZmlsZXMgPC0gbGVuZ3RoKGxpc3QuZmlsZXModHJhaW5faW1hZ2VfZGlyKSkNCg0KaW1hZ2VfbGlzdCA8LSBsaXN0KCkNCmZvcihpIGluIDE6MTAwKXsNCiAgIGltYWdlX2xpc3RbW2ldXSA8LSByZWFkSW1hZ2UocGFzdGUwKHRyYWluX2ltYWdlX2Rpciwgc3ByaW50ZigiJTA0ZCIsIGkpLCAiLmpwZyIpKQ0KfQ0KYGBgDQoNCkZpZHVjaWFsIHBvaW50cyBhcmUgc3RvcmVkIGluIG1hdGxhYiBmb3JtYXQuIEluIHRoaXMgc3RlcCwgd2UgcmVhZCB0aGVtIGFuZCBzdG9yZSB0aGVtIGluIGEgbGlzdC4NCmBgYHtyIHJlYWQgZmlkdWNpYWwgcG9pbnRzfQ0KI2Z1bmN0aW9uIHRvIHJlYWQgZmlkdWNpYWwgcG9pbnRzDQojaW5wdXQ6IGluZGV4DQojb3V0cHV0OiBtYXRyaXggb2YgZmlkdWNpYWwgcG9pbnRzIGNvcnJlc3BvbmRpbmcgdG8gdGhlIGluZGV4DQpyZWFkTWF0Lm1hdHJpeCA8LSBmdW5jdGlvbihpbmRleCl7DQogICAgIHJldHVybihyb3VuZChyZWFkTWF0KHBhc3RlMCh0cmFpbl9wdF9kaXIsIHNwcmludGYoIiUwNGQiLCBpbmRleCksICIubWF0IikpW1sxXV0sMCkpDQp9DQoNCiNsb2FkIGZpZHVjaWFsIHBvaW50cw0KZmlkdWNpYWxfcHRfbGlzdCA8LSBsYXBwbHkoMTpuX2ZpbGVzLCByZWFkTWF0Lm1hdHJpeCkNCnNhdmUoZmlkdWNpYWxfcHRfbGlzdCwgZmlsZT0iLi4vb3V0cHV0L2ZpZHVjaWFsX3B0X2xpc3QuUkRhdGEiKQ0KYGBgDQoNCiMjIyBTdGVwIDM6IGNvbnN0cnVjdCBmZWF0dXJlcyBhbmQgcmVzcG9uc2VzDQoNCisgVGhlIGZvbGxvdyBwbG90cyBzaG93IGhvdyBwYWlyd2lzZSBkaXN0YW5jZSBiZXR3ZWVuIGZpZHVjaWFsIHBvaW50cyBjYW4gd29yayBhcyBmZWF0dXJlIGZvciBmYWNpYWwgZW1vdGlvbiByZWNvZ25pdGlvbi4NCg0KICArIEluIHRoZSBmaXJzdCBjb2x1bW4sIDc4IGZpZHVjaWFscyBwb2ludHMgb2YgZWFjaCBlbW90aW9uIGFyZSBtYXJrZWQgaW4gb3JkZXIuIA0KICArIEluIHRoZSBzZWNvbmQgY29sdW1uIGRpc3RyaWJ1dGlvbnMgb2YgdmVydGljYWwgZGlzdGFuY2UgYmV0d2VlbiByaWdodCBwdXBpbCgxKSBhbmQgIHJpZ2h0IGJyb3cgcGVhaygyMSkgYXJlIHNob3duIGluICBoaXN0b2dyYW1zLiBGb3IgZXhhbXBsZSwgdGhlIGRpc3RhbmNlIG9mIGFuIGFuZ3J5IGZhY2UgdGVuZHMgdG8gYmUgc2hvcnRlciB0aGFuIHRoYXQgb2YgYSBzdXJwcmlzZWQgZmFjZS4NCiAgKyBUaGUgdGhpcmQgY29sdW1uIGlzIHRoZSBkaXN0cmlidXRpb25zIG9mIHZlcnRpY2FsIGRpc3RhbmNlcyBiZXR3ZWVuIHJpZ2h0IG1vdXRoIGNvcm5lcig1MCkNCmFuZCB0aGUgbWlkcG9pbnQgb2YgdGhlIHVwcGVyIGxpcCg1MikuICBGb3IgZXhhbXBsZSwgdGhlIGRpc3RhbmNlIG9mIGFuIGhhcHB5IGZhY2UgdGVuZHMgdG8gYmUgc2hvcnRlciB0aGFuIHRoYXQgb2YgYSBzYWQgZmFjZS4NCg0KIVtGaWd1cmUxXSguLi9maWdzL2ZlYXR1cmVfdmlzdWFsaXphdGlvbi5qcGcpDQoNCmBmZWF0dXJlLlJgIHNob3VsZCBiZSB0aGUgd3JhcHBlciBmb3IgYWxsIHlvdXIgZmVhdHVyZSBlbmdpbmVlcmluZyBmdW5jdGlvbnMgYW5kIG9wdGlvbnMuIFRoZSBmdW5jdGlvbiBgZmVhdHVyZSggKWAgc2hvdWxkIGhhdmUgb3B0aW9ucyB0aGF0IGNvcnJlc3BvbmQgdG8gZGlmZmVyZW50IHNjZW5hcmlvcyBmb3IgeW91ciBwcm9qZWN0IGFuZCBwcm9kdWNlcyBhbiBSIG9iamVjdCB0aGF0IGNvbnRhaW5zIGZlYXR1cmVzIGFuZCByZXNwb25zZXMgdGhhdCBhcmUgcmVxdWlyZWQgYnkgYWxsIHRoZSBtb2RlbHMgeW91IGFyZSBnb2luZyB0byBldmFsdWF0ZSBsYXRlci4gDQogIA0KICArIGBmZWF0dXJlLlJgDQogICsgSW5wdXQ6IGxpc3Qgb2YgaW1hZ2VzIG9yIGZpZHVjaWFsIHBvaW50DQogICsgT3V0cHV0OiBhbiBSRGF0YSBmaWxlIHRoYXQgY29udGFpbnMgZXh0cmFjdGVkIGZlYXR1cmVzIGFuZCBjb3JyZXNwb25kaW5nIHJlc3BvbnNlcw0KDQpgYGB7ciBmZWF0dXJlfQ0Kc291cmNlKCIuLi9saWIvZmVhdHVyZS5SIikNCnRtX2ZlYXR1cmVfdHJhaW4gPC0gTkENCmlmKHJ1bi5mZWF0dXJlLnRyYWluKXsNCiAgdG1fZmVhdHVyZV90cmFpbiA8LSBzeXN0ZW0udGltZShkYXRfdHJhaW4gPC0gZmVhdHVyZShmaWR1Y2lhbF9wdF9saXN0LCB0cmFpbl9pZHgpKQ0KICBzYXZlKGRhdF90cmFpbiwgZmlsZT0iLi4vb3V0cHV0L2ZlYXR1cmVfdHJhaW4uUkRhdGEiKQ0KfWVsc2V7DQogIGxvYWQoZmlsZT0iLi4vb3V0cHV0L2ZlYXR1cmVfdHJhaW4uUkRhdGEiKQ0KfQ0KDQp0bV9mZWF0dXJlX3Rlc3QgPC0gTkENCmlmKHJ1bi5mZWF0dXJlLnRlc3Qpew0KICB0bV9mZWF0dXJlX3Rlc3QgPC0gc3lzdGVtLnRpbWUoZGF0X3Rlc3QgPC0gZmVhdHVyZShmaWR1Y2lhbF9wdF9saXN0LCB0ZXN0X2lkeCkpDQogIHNhdmUoZGF0X3Rlc3QsIGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3Rlc3QuUkRhdGEiKQ0KfWVsc2V7DQogIGxvYWQoZmlsZT0iLi4vb3V0cHV0L2ZlYXR1cmVfdGVzdC5SRGF0YSIpDQp9DQoNCg0KYGBgDQoNCiMjIyBHQk0NCg0KIyMjIFN0ZXAgNDogVHJhaW4gYSBjbGFzc2lmaWNhdGlvbiBtb2RlbCB3aXRoIHRyYWluaW5nIGZlYXR1cmVzIGFuZCByZXNwb25zZXMNCkNhbGwgdGhlIHRyYWluIG1vZGVsIGFuZCB0ZXN0IG1vZGVsIGZyb20gbGlicmFyeS4gDQoNCmB0cmFpbi5SYCBhbmQgYHRlc3QuUmAgc2hvdWxkIGJlIHdyYXBwZXJzIGZvciBhbGwgeW91ciBtb2RlbCB0cmFpbmluZyBzdGVwcyBhbmQgeW91ciBjbGFzc2lmaWNhdGlvbi9wcmVkaWN0aW9uIHN0ZXBzLiANCg0KKyBgdHJhaW4uUmANCiAgKyBJbnB1dDogYSBkYXRhIGZyYW1lIGNvbnRhaW5pbmcgZmVhdHVyZXMgYW5kIGxhYmVscyBhbmQgYSBwYXJhbWV0ZXIgbGlzdC4NCiAgKyBPdXRwdXQ6YSB0cmFpbmVkIG1vZGVsDQorIGB0ZXN0LlJgDQogICsgSW5wdXQ6IHRoZSBmaXR0ZWQgY2xhc3NpZmljYXRpb24gbW9kZWwgdXNpbmcgdHJhaW5pbmcgZGF0YSBhbmQgcHJvY2Vzc2VkIGZlYXR1cmVzIGZyb20gdGVzdGluZyBpbWFnZXMgDQogICsgSW5wdXQ6IGFuIFIgb2JqZWN0IHRoYXQgY29udGFpbnMgYSB0cmFpbmVkIGNsYXNzaWZpZXIuDQogICsgT3V0cHV0OiB0cmFpbmluZyBtb2RlbCBzcGVjaWZpY2F0aW9uDQoNCisgSW4gdGhpcyBwYXJ0LCB3ZSB1c2UgR0JNIChiYXNlbGluZSBtb2RlbCkgdG8gZG8gY2xhc3NpZmljYXRpb24uDQoNCmBgYHtyIGxvYWRsaWJfZ2JtfQ0Kc291cmNlKCIuLi9saWIvdHJhaW5fZ2JtLlIiKSANCnNvdXJjZSgiLi4vbGliL3Rlc3RfZ2JtLlIiKQ0KYGBgDQoNCiMjIyMgTW9kZWwgc2VsZWN0aW9uIHdpdGggY3Jvc3MtdmFsaWRhdGlvbg0KKiBEbyBtb2RlbCBzZWxlY3Rpb24gYnkgY2hvb3NpbmcgYW1vbmcgZGlmZmVyZW50IHZhbHVlcyBvZiB0cmFpbmluZyBtb2RlbCBwYXJhbWV0ZXJzLg0KDQpgYGB7ciBydW5jdn0NCnNvdXJjZSgiLi4vbGliL2Nyb3NzX3ZhbGlkYXRpb25fZ2JtLlIiKQ0KZmVhdHVyZV90cmFpbiA9IGFzLm1hdHJpeChkYXRfdHJhaW5bLCAtNjAwN10pDQpsYWJlbF90cmFpbiA9IGFzLmludGVnZXIoZGF0X3RyYWluJGxhYmVsKQ0KDQojIERFTEVURQ0Kc2F2ZShmZWF0dXJlX3RyYWluLCBmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV90cmFpbl90bXBfb25seS5SRGF0YSIpDQpzYXZlKGxhYmVsX3RyYWluLCBmaWxlPSIuLi9vdXRwdXQvbGFiZWxfdHJhaW5fdG1wX29ubHkuUkRhdGEiKQ0KIyBERUxFVEUNCg0KaWYocnVuLmN2X2dibSl7DQogIHJlc19jdl9nYm0gPC0gbWF0cml4KDAsIG5yb3cgPSBsZW5ndGgobi50cmVlcykgKiBsZW5ndGgoc2hyaW5rYWdlKSwgbmNvbCA9IDYpDQogIGNvdW50ID0gMA0KICBmb3IoaSBpbiAxOmxlbmd0aChuLnRyZWVzKSl7DQogICAgZm9yKGogaW4gMTpsZW5ndGgoc2hyaW5rYWdlKSl7DQogICAgICBjb3VudCA9IGNvdW50ICsgMQ0KICAgICAgY2F0KCJuLnRyZWVzID0iLCBuLnRyZWVzW2ldLCAiXG4iKQ0KICAgICAgY2F0KCJzaHJpbmthZ2UgPSIsIHNocmlua2FnZVtqXSwgIlxuIikNCiAgICAgIA0KICAgICAgcmVzX2N2IDwtIGN2LmZ1bmN0aW9uX2dibShmZWF0dXJlcyA9IGZlYXR1cmVfdHJhaW4sIGxhYmVscyA9IGxhYmVsX3RyYWluLCBLLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbi50cmVlc1tpXSwgc2hyaW5rYWdlW2pdLCByZXdlaWdodCA9IHNhbXBsZS5yZXdlaWdodCkNCiAgICAgIA0KICAgICAgcmVzX2N2X2dibVtjb3VudCxdIDwtIGMobi50cmVlc1tpXSwgc2hyaW5rYWdlW2pdLCByZXNfY3ZbMV0sIHJlc19jdlsyXSwgcmVzX2N2WzNdLCByZXNfY3ZbNF0pDQogICAgfQ0KICB9DQogIA0KICBjb2xuYW1lcyhyZXNfY3ZfZ2JtKSA8LSBjKCJuLnRyZWVzIiwic2hyaW5rYWdlIiwibWVhbl9lcnJvciIsICJzZF9lcnJvciIsICJtZWFuX0FVQyIsICJzZF9BVUMiKQ0KICBzYXZlKHJlc19jdl9nYm0sIGZpbGU9Ii4uL291dHB1dC9yZXNfY3ZfZ2JtLlJEYXRhIikNCn1lbHNlew0KICBsb2FkKCIuLi9vdXRwdXQvcmVzX2N2X2dibS5SRGF0YSIpDQp9DQpgYGANCg0KVmlzdWFsaXplIGNyb3NzLXZhbGlkYXRpb24gcmVzdWx0cy4gDQpgYGB7ciBjdl92aXN9DQpyZXNfY3ZfZ2JtIDwtIGFzLmRhdGEuZnJhbWUocmVzX2N2X2dibSkgDQoNCmlmKHJ1bi5jdl9nYm0pew0KICBwMSA8LSByZXNfY3ZfZ2JtICU+JSANCiAgICBnZ3Bsb3QoYWVzKHggPSBuLnRyZWVzLCB5ID0gbWVhbl9lcnJvciwNCiAgICAgICAgICAgICAgIHltaW4gPSBtZWFuX2Vycm9yIC0gc2RfZXJyb3IsIHltYXggPSBtZWFuX2Vycm9yICsgc2RfZXJyb3IpKSArIA0KICAgIGdlb21fY3Jvc3NiYXIoKSArDQogICAgZmFjZXRfd3JhcCh+c2hyaW5rYWdlKSsNCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKQ0KICANCiAgcDIgPC0gcmVzX2N2X2dibSAlPiUgDQogICAgZ2dwbG90KGFlcyh4ID0gbi50cmVlcywgeSA9IG1lYW5fQVVDLA0KICAgICAgICAgICAgICAgeW1pbiA9IG1lYW5fQVVDIC0gc2RfQVVDLCB5bWF4ID0gbWVhbl9BVUMgKyBzZF9BVUMpKSArIA0KICAgIGdlb21fY3Jvc3NiYXIoKSArDQogICAgZmFjZXRfd3JhcCh+c2hyaW5rYWdlKSsNCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKQ0KICANCiAgcHJpbnQocDEpDQogIHByaW50KHAyKQ0KfQ0KDQpgYGANCg0KDQoqIENob29zZSB0aGUgImJlc3QiIHBhcmFtZXRlciB2YWx1ZQ0KYGBge3IgYmVzdF9tb2RlbH0NCiMgcGFyX24udHJlZXNfYmVzdCA8LSBhcy5udW1lcmljKHJlc19jdl9nYm1bd2hpY2gubWluKHJlc19jdl9nYm0kbWVhbl9lcnJvciksIDFdKQ0KIyBwYXJfc2hyaW5rYWdlX2Jlc3QgPC0gYXMubnVtZXJpYyhyZXNfY3ZfZ2JtW3doaWNoLm1pbihyZXNfY3ZfZ2JtJG1lYW5fZXJyb3IpLCAyXSkNCg0KcGFyX24udHJlZXNfYmVzdCA8LSA1MDANCnBhcl9zaHJpbmthZ2VfYmVzdCA8LSAwLjA1DQpgYGANCg0KKiBUcmFpbiB0aGUgbW9kZWwgd2l0aCB0aGUgZW50aXJlIHRyYWluaW5nIHNldCB1c2luZyB0aGUgc2VsZWN0ZWQgbW9kZWwgKG1vZGVsIHBhcmFtZXRlcikgdmlhIGNyb3NzLXZhbGlkYXRpb24uDQpgYGB7ciBmaW5hbF90cmFpbn0NCiMgdHJhaW5pbmcgd2VpZ2h0cw0Kd2VpZ2h0X3RyYWluIDwtIHJlcChOQSwgbGVuZ3RoKGxhYmVsX3RyYWluKSkNCmZvciAodiBpbiB1bmlxdWUobGFiZWxfdHJhaW4pKXsNCiAgd2VpZ2h0X3RyYWluW2xhYmVsX3RyYWluID09IHZdID0gMC41ICogbGVuZ3RoKGxhYmVsX3RyYWluKSAvIGxlbmd0aChsYWJlbF90cmFpbltsYWJlbF90cmFpbiA9PSB2XSkNCn0NCg0KdG1fdHJhaW4gPC0gTkENCmlmIChzYW1wbGUucmV3ZWlnaHQpew0KICB0bV90cmFpbiA8LSBzeXN0ZW0udGltZShmaXRfdHJhaW4gPC0gdHJhaW5fZ2JtKGZlYXR1cmVfdHJhaW4sIGxhYmVsX3RyYWluLCB3ID0gd2VpZ2h0X3RyYWluLCBwYXJfbi50cmVlc19iZXN0LCBwYXJfc2hyaW5rYWdlX2Jlc3QpKQ0KfSBlbHNlIHsNCiAgdG1fdHJhaW4gPC0gc3lzdGVtLnRpbWUoZml0X3RyYWluIDwtIHRyYWluX2dibShmZWF0dXJlX3RyYWluLCBsYWJlbF90cmFpbiwgdyA9IE5VTEwsIHBhcl9uLnRyZWVzX2Jlc3QsIHBhcl9zaHJpbmthZ2VfYmVzdCkpDQp9DQpzYXZlKGZpdF90cmFpbiwgZmlsZT0iLi4vb3V0cHV0L2ZpdF90cmFpbl9nYm0uUkRhdGEiKQ0KYGBgDQoNCiMjIyBTdGVwIDU6IFJ1biB0ZXN0IG9uIHRlc3QgaW1hZ2VzDQpgYGB7ciB0ZXN0fQ0KdG1fdGVzdCA9IE5BDQpmZWF0dXJlX3Rlc3QgPC0gYXMubWF0cml4KGRhdF90ZXN0WywgLTYwMDddKQ0KaWYocnVuLnRlc3RfZ2JtKXsNCiAgbG9hZChmaWxlPSIuLi9vdXRwdXQvZml0X3RyYWluX2dibS5SRGF0YSIpDQogIHRtX3Rlc3QgPC0gc3lzdGVtLnRpbWUoe3Byb2JfcHJlZCA8LSB0ZXN0X2dibShmaXRfdHJhaW4sIGZlYXR1cmVfdGVzdCwgcGFyX24udHJlZXNfYmVzdCwgcHJlZC50eXBlID0gJ3Jlc3BvbnNlJyk7fSkNCn0NCmBgYA0KDQoNCiogZXZhbHVhdGlvbg0KYGBge3J9DQojIyByZXdlaWdodCB0aGUgdGVzdCBkYXRhIHRvIHJlcHJlc2VudCBhIGJhbGFuY2VkIGxhYmVsIGRpc3RyaWJ1dGlvbg0KbGFiZWxfdGVzdCA8LSBhcy5pbnRlZ2VyKGRhdF90ZXN0JGxhYmVsKQ0KDQojIERFTEVURQ0Kc2F2ZShmZWF0dXJlX3Rlc3QsIGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3Rlc3RfdG1wX29ubHkuUkRhdGEiKQ0Kc2F2ZShsYWJlbF90ZXN0LCBmaWxlPSIuLi9vdXRwdXQvbGFiZWxfdGVzdF90bXBfb25seS5SRGF0YSIpDQojIERFTEVURQ0KDQp3ZWlnaHRfdGVzdCA8LSByZXAoTkEsIGxlbmd0aChsYWJlbF90ZXN0KSkNCmZvciAodiBpbiB1bmlxdWUobGFiZWxfdGVzdCkpew0KICB3ZWlnaHRfdGVzdFtsYWJlbF90ZXN0ID09IHZdID0gMC41ICogbGVuZ3RoKGxhYmVsX3Rlc3QpIC8gbGVuZ3RoKGxhYmVsX3Rlc3RbbGFiZWxfdGVzdCA9PSB2XSkNCn0NCg0KbGFiZWxfcHJlZCA8LSBpZmVsc2UocHJvYl9wcmVkID49IDAuNSwgMSwgMCkNCmxhYmVsX3Rlc3QgPC0gaWZlbHNlKGxhYmVsX3Rlc3QgPT0gMiwgMCwgMSkNCg0KYWNjdSA8LSBzdW0od2VpZ2h0X3Rlc3QgKiAobGFiZWxfcHJlZCA9PSBsYWJlbF90ZXN0KSkgLyBzdW0od2VpZ2h0X3Rlc3QpDQp0cHIuZnByIDwtIFdlaWdodGVkUk9DKHByb2JfcHJlZCwgbGFiZWxfdGVzdCwgd2VpZ2h0X3Rlc3QpDQphdWMgPC0gV2VpZ2h0ZWRBVUModHByLmZwcikNCg0KDQpjYXQoIlRoZSBhY2N1cmFjeSBvZiBtb2RlbDoiLCAiR0JNIHdpdGggbi50cmVlcyA9IiAsIHBhcl9uLnRyZWVzX2Jlc3QsICJhbmQgc2hyaW5rYWdlID0iLCBwYXJfc2hyaW5rYWdlX2Jlc3QsICJpcyIsIGFjY3UqMTAwLCAiJS5cbiIpDQpjYXQoIlRoZSBBVUMgb2YgbW9kZWw6IiwgIkdCTSB3aXRoIG4udHJlZXMgPSIgLCBwYXJfbi50cmVlc19iZXN0LCAiYW5kIHNocmlua2FnZSA9IiwgcGFyX3Nocmlua2FnZV9iZXN0LCAiaXMiLCBhdWMsICIuXG4iKQ0KDQoNCmBgYA0KDQojIyMgU3VtbWFyaXplIFJ1bm5pbmcgVGltZQ0KUHJlZGljdGlvbiBwZXJmb3JtYW5jZSBtYXR0ZXJzLCBzbyBkb2VzIHRoZSBydW5uaW5nIHRpbWVzIGZvciBjb25zdHJ1Y3RpbmcgZmVhdHVyZXMgYW5kIGZvciB0cmFpbmluZyB0aGUgbW9kZWwsIGVzcGVjaWFsbHkgd2hlbiB0aGUgY29tcHV0YXRpb24gcmVzb3VyY2UgaXMgbGltaXRlZC4gDQpgYGB7ciBydW5uaW5nX3RpbWV9DQpjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0cmFpbmluZyBmZWF0dXJlcz0iLCB0bV9mZWF0dXJlX3RyYWluWzNdLCAicyBcbiIpDQpjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0ZXN0aW5nIGZlYXR1cmVzPSIsIHRtX2ZlYXR1cmVfdGVzdFszXSwgInMgXG4iKQ0KY2F0KCJUaW1lIGZvciB0cmFpbmluZyBtb2RlbD0iLCB0bV90cmFpblszXSwgInMgXG4iKSANCmNhdCgiVGltZSBmb3IgdGVzdGluZyBtb2RlbD0iLCB0bV90ZXN0WzNdLCAicyBcbiIpDQpgYGANCg0KIyMjIEROTg0KDQojIyMgU3RlcCA0OiBUcmFpbiBhIGNsYXNzaWZpY2F0aW9uIG1vZGVsIHdpdGggdHJhaW5pbmcgZmVhdHVyZXMgYW5kIHJlc3BvbnNlcw0KQ2FsbCB0aGUgdHJhaW4gbW9kZWwgYW5kIHRlc3QgbW9kZWwgZnJvbSBsaWJyYXJ5LiANCg0KYHRyYWluLlJgIGFuZCBgdGVzdC5SYCBzaG91bGQgYmUgd3JhcHBlcnMgZm9yIGFsbCB5b3VyIG1vZGVsIHRyYWluaW5nIHN0ZXBzIGFuZCB5b3VyIGNsYXNzaWZpY2F0aW9uL3ByZWRpY3Rpb24gc3RlcHMuIA0KDQorIGB0cmFpbi5SYA0KICArIElucHV0OiBhIGRhdGEgZnJhbWUgY29udGFpbmluZyBmZWF0dXJlcyBhbmQgbGFiZWxzIGFuZCBhIHBhcmFtZXRlciBsaXN0Lg0KICArIE91dHB1dDphIHRyYWluZWQgbW9kZWwNCisgYHRlc3QuUmANCiAgKyBJbnB1dDogdGhlIGZpdHRlZCBjbGFzc2lmaWNhdGlvbiBtb2RlbCB1c2luZyB0cmFpbmluZyBkYXRhIGFuZCBwcm9jZXNzZWQgZmVhdHVyZXMgZnJvbSB0ZXN0aW5nIGltYWdlcyANCiAgKyBJbnB1dDogYW4gUiBvYmplY3QgdGhhdCBjb250YWlucyBhIHRyYWluZWQgY2xhc3NpZmllci4NCiAgKyBPdXRwdXQ6IHRyYWluaW5nIG1vZGVsIHNwZWNpZmljYXRpb24NCg0KKyBJbiB0aGlzIHBhcnQsIHdlIHVzZSBHQk0gKGJhc2VsaW5lIG1vZGVsKSB0byBkbyBjbGFzc2lmaWNhdGlvbi4NCg0KYGBge3IgbG9hZGxpYl9kbm59DQpzb3VyY2UoIi4uL2xpYi90cmFpbl9kbm4uUiIpIA0Kc291cmNlKCIuLi9saWIvdGVzdF9kbm4uUiIpDQpgYGANCg0KIyMjIyBNb2RlbCBzZWxlY3Rpb24gd2l0aCBjcm9zcy12YWxpZGF0aW9uDQoqIERvIG1vZGVsIHNlbGVjdGlvbiBieSBjaG9vc2luZyBhbW9uZyBkaWZmZXJlbnQgdmFsdWVzIG9mIHRyYWluaW5nIG1vZGVsIHBhcmFtZXRlcnMuDQoNCmBgYHtyIHJ1bmN2X2Rubn0NCnNvdXJjZSgiLi4vbGliL2Nyb3NzX3ZhbGlkYXRpb25fZG5uLlIiKQ0KZmVhdHVyZV90cmFpbiA9IGFzLm1hdHJpeChkYXRfdHJhaW5bLCAtNjAwN10pDQpsYWJlbF90cmFpbiA9IGFzLmludGVnZXIoZGF0X3RyYWluJGxhYmVsKQ0KDQojIGlmKHJ1bi5jdl9kbm4pew0KIyAgIHJlc19jdl9kbm4gPC0gbWF0cml4KDAsIDEsIG5jb2wgPSA0KQ0KIyAgIA0KIyAgIHJlc19jdiA8LSBjdi5mdW5jdGlvbl9kbm4oZmVhdHVyZXMgPSBmZWF0dXJlX3RyYWluLCBsYWJlbHMgPSBsYWJlbF90cmFpbiwgSywNCiMgICAgICAgICAgICAgICAgICAgICAgICAgICByZXdlaWdodCA9IHNhbXBsZS5yZXdlaWdodCkNCiMgICANCiMgICByZXNfY3ZfZG5uWzEsXSA8LSBjKHJlc19jdlsxXSwgcmVzX2N2WzJdLCByZXNfY3ZbM10sIHJlc19jdls0XSkNCiMgICANCiMgICBjb2xuYW1lcyhyZXNfY3ZfZG5uKSA8LSBjKCJtZWFuX2Vycm9yIiwgInNkX2Vycm9yIiwgIm1lYW5fQVVDIiwgInNkX0FVQyIpDQojICAgc2F2ZShyZXNfY3ZfZG5uLCBmaWxlPSIuLi9vdXRwdXQvcmVzX2N2X2Rubi5SRGF0YSIpDQojIH1lbHNlew0KIyAgIGxvYWQoIi4uL291dHB1dC9yZXNfY3ZfZG5uLlJEYXRhIikNCiMgfQ0KYGBgDQoNClZpc3VhbGl6ZSBjcm9zcy12YWxpZGF0aW9uIHJlc3VsdHMuIA0KYGBge3IgY3ZfdmlzX2Rubn0NCiMgcmVzX2N2X2RubiA8LSBhcy5kYXRhLmZyYW1lKHJlc19jdl9kbm4pIA0KIyANCiMgaWYocnVuLmN2X2Rubil7DQojICAgcDEgPC0gcmVzX2N2X2RubiAlPiUgDQojICAgICBnZ3Bsb3QoYWVzKHggPSAiRE5OIiwgeSA9IG1lYW5fZXJyb3IsDQojICAgICAgICAgICAgICAgIHltaW4gPSBtZWFuX2Vycm9yIC0gc2RfZXJyb3IsIHltYXggPSBtZWFuX2Vycm9yICsgc2RfZXJyb3IpKSArIA0KIyAgICAgZ2VvbV9jcm9zc2JhcigpICsNCiMgICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KCkpDQojICAgDQojICAgcDIgPC0gcmVzX2N2X2RubiAlPiUgDQojICAgICBnZ3Bsb3QoYWVzKHggPSAiRE5OIiwgeSA9IG1lYW5fQVVDLA0KIyAgICAgICAgICAgICAgICB5bWluID0gbWVhbl9BVUMgLSBzZF9BVUMsIHltYXggPSBtZWFuX0FVQyArIHNkX0FVQykpICsgDQojICAgICBnZW9tX2Nyb3NzYmFyKCkgKw0KIyAgICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoKSkNCiMgICANCiMgICBwcmludChwMSkNCiMgICBwcmludChwMikNCiMgfQ0KDQpgYGANCg0KDQoqIENob29zZSB0aGUgImJlc3QiIHBhcmFtZXRlciB2YWx1ZQ0KYGBge3IgYmVzdF9tb2RlbF9kbm59DQojIHBhcl9uLnRyZWVzX2Jlc3QgPC0gYXMubnVtZXJpYyhyZXNfY3ZfZ2JtW3doaWNoLm1pbihyZXNfY3ZfZ2JtJG1lYW5fZXJyb3IpLCAxXSkNCiMgcGFyX3Nocmlua2FnZV9iZXN0IDwtIGFzLm51bWVyaWMocmVzX2N2X2dibVt3aGljaC5taW4ocmVzX2N2X2dibSRtZWFuX2Vycm9yKSwgMl0pDQpgYGANCg0KKiBUcmFpbiB0aGUgbW9kZWwgd2l0aCB0aGUgZW50aXJlIHRyYWluaW5nIHNldCB1c2luZyB0aGUgc2VsZWN0ZWQgbW9kZWwgKG1vZGVsIHBhcmFtZXRlcikgdmlhIGNyb3NzLXZhbGlkYXRpb24uDQpgYGB7ciBmaW5hbF90cmFpbl9kbm59DQpzb3VyY2UoIi4uL2xpYi90cmFpbl9kbm4uUiIpIA0Kc291cmNlKCIuLi9saWIvdGVzdF9kbm4uUiIpDQoNCmZlYXR1cmVfdHJhaW4gPSBhcy5tYXRyaXgoZGF0X3RyYWluWywgLTYwMDddKQ0KbGFiZWxfdHJhaW4gPSBhcy5pbnRlZ2VyKGRhdF90cmFpbiRsYWJlbCkNCmZlYXR1cmVfdGVzdCA8LSBhcy5tYXRyaXgoZGF0X3Rlc3RbLCAtNjAwN10pDQpsYWJlbF90ZXN0IDwtIGFzLmludGVnZXIoZGF0X3Rlc3QkbGFiZWwpDQpsYWJlbF90ZXN0IDwtIGlmZWxzZShsYWJlbF90ZXN0ID09IDIsIDAsIDEpDQp3ZWlnaHRfdGVzdCA8LSByZXAoTkEsIGxlbmd0aChsYWJlbF90ZXN0KSkNCg0KIyB0cmFpbmluZyB3ZWlnaHRzDQp3ZWlnaHRfdHJhaW4gPC0gcmVwKE5BLCBsZW5ndGgobGFiZWxfdHJhaW4pKQ0KZm9yICh2IGluIHVuaXF1ZShsYWJlbF90cmFpbikpew0KICB3ZWlnaHRfdHJhaW5bbGFiZWxfdHJhaW4gPT0gdl0gPSAwLjUgKiBsZW5ndGgobGFiZWxfdHJhaW4pIC8gbGVuZ3RoKGxhYmVsX3RyYWluW2xhYmVsX3RyYWluID09IHZdKQ0KfQ0KDQpmb3IgKHYgaW4gdW5pcXVlKGxhYmVsX3Rlc3QpKXsNCiAgd2VpZ2h0X3Rlc3RbbGFiZWxfdGVzdCA9PSB2XSA9IDAuNSAqIGxlbmd0aChsYWJlbF90ZXN0KSAvIGxlbmd0aChsYWJlbF90ZXN0W2xhYmVsX3Rlc3QgPT0gdl0pDQp9DQoNCiMgdmFsaWRhdGlvbl9zcGxpdCA8LSAxIC8gNQ0KIyB2YWxpZGF0aW9uX3NwbGl0IDwtIDANCnRtX3RyYWluIDwtIE5BDQppZiAoc2FtcGxlLnJld2VpZ2h0KXsNCiAgdG1fdHJhaW4gPC0gc3lzdGVtLnRpbWUoZml0X3RyYWluIDwtIHRyYWluX2RubihmZWF0dXJlX3RyYWluLCBsYWJlbF90cmFpbiwgdyA9IHdlaWdodF90cmFpbiwgZmVhdHVyZV90ZXN0LCBsYWJlbF90ZXN0LCB3ZWlnaHRfdGVzdCkpDQp9IGVsc2Ugew0KICB0bV90cmFpbiA8LSBzeXN0ZW0udGltZShmaXRfdHJhaW4gPC0gdHJhaW5fZG5uKGZlYXR1cmVfdHJhaW4sIGxhYmVsX3RyYWluLCB3ID0gTlVMTCwgZmVhdHVyZV90ZXN0LCBsYWJlbF90ZXN0LCB3ZWlnaHRfdGVzdCA9IE5VTEwpKQ0KfQ0Kc2F2ZV9tb2RlbF9oZGY1KGZpdF90cmFpbiwgIi4uL291dHB1dC9maXRfdHJhaW5fZG5uLmg1IikNCmBgYA0KDQojIyMgU3RlcCA1OiBSdW4gdGVzdCBvbiB0ZXN0IGltYWdlcw0KYGBge3IgdGVzdF9kbm59DQp0bV90ZXN0ID0gTkENCmZlYXR1cmVfdGVzdCA8LSBhcy5tYXRyaXgoZGF0X3Rlc3RbLCAtNjAwN10pDQppZihydW4udGVzdF9kbm4pew0KICBmaXRfdHJhaW4gPC0gbG9hZF9tb2RlbF9oZGY1KCIuLi9vdXRwdXQvZml0X3RyYWluX2Rubi5oNSIpDQogIHRtX3Rlc3QgPC0gc3lzdGVtLnRpbWUoe2xhYmVsX3ByZWQgPC0gdGVzdF9kbm4oZml0X3RyYWluLCBmZWF0dXJlX3Rlc3QsIHR5cGUgPSAicHJlZGljdF9jbGFzc2VzIik7DQogICBwcm9iX3ByZWQgPC0gdGVzdF9kbm4oZml0X3RyYWluLCBmZWF0dXJlX3Rlc3QsIHR5cGUgPSAicHJlZGljdF9wcm9iYSIpfSkNCn0NCmBgYA0KDQoNCiogZXZhbHVhdGlvbg0KYGBge3J9DQojIyByZXdlaWdodCB0aGUgdGVzdCBkYXRhIHRvIHJlcHJlc2VudCBhIGJhbGFuY2VkIGxhYmVsIGRpc3RyaWJ1dGlvbg0KbGFiZWxfdGVzdCA8LSBhcy5pbnRlZ2VyKGRhdF90ZXN0JGxhYmVsKQ0KDQp3ZWlnaHRfdGVzdCA8LSByZXAoTkEsIGxlbmd0aChsYWJlbF90ZXN0KSkNCmZvciAodiBpbiB1bmlxdWUobGFiZWxfdGVzdCkpew0KICB3ZWlnaHRfdGVzdFtsYWJlbF90ZXN0ID09IHZdID0gMC41ICogbGVuZ3RoKGxhYmVsX3Rlc3QpIC8gbGVuZ3RoKGxhYmVsX3Rlc3RbbGFiZWxfdGVzdCA9PSB2XSkNCn0NCg0KbGFiZWxfdGVzdCA8LSBpZmVsc2UobGFiZWxfdGVzdCA9PSAyLCAwLCAxKQ0KDQphY2N1IDwtIHN1bSh3ZWlnaHRfdGVzdCAqIChsYWJlbF9wcmVkID09IGxhYmVsX3Rlc3QpKSAvIHN1bSh3ZWlnaHRfdGVzdCkNCg0KIyBwcm9iX3ByZWQgPC0gYXBwbHkocHJvYl9wcmVkLCAxLCBtYXgpDQpwcm9iX3ByZWQgPC0gcHJvYl9wcmVkWywgMl0NCg0KdHByLmZwciA8LSBXZWlnaHRlZFJPQyhwcm9iX3ByZWQsIGxhYmVsX3Rlc3QsIHdlaWdodF90ZXN0KQ0KYXVjIDwtIFdlaWdodGVkQVVDKHRwci5mcHIpDQoNCg0KY2F0KCJUaGUgYWNjdXJhY3kgb2YgbW9kZWw6IiwgIkROTiIsICJpcyIsIGFjY3UqMTAwLCAiJS5cbiIpDQpjYXQoIlRoZSBBVUMgb2YgbW9kZWw6IiwgIkROTiIsICJpcyIsIGF1YywgIi5cbiIpDQoNCg0KYGBgDQoNCiMjIyBTdW1tYXJpemUgUnVubmluZyBUaW1lDQpQcmVkaWN0aW9uIHBlcmZvcm1hbmNlIG1hdHRlcnMsIHNvIGRvZXMgdGhlIHJ1bm5pbmcgdGltZXMgZm9yIGNvbnN0cnVjdGluZyBmZWF0dXJlcyBhbmQgZm9yIHRyYWluaW5nIHRoZSBtb2RlbCwgZXNwZWNpYWxseSB3aGVuIHRoZSBjb21wdXRhdGlvbiByZXNvdXJjZSBpcyBsaW1pdGVkLiANCmBgYHtyIHJ1bm5pbmdfdGltZV9kbm59DQpjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0cmFpbmluZyBmZWF0dXJlcz0iLCB0bV9mZWF0dXJlX3RyYWluWzNdLCAicyBcbiIpDQpjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0ZXN0aW5nIGZlYXR1cmVzPSIsIHRtX2ZlYXR1cmVfdGVzdFszXSwgInMgXG4iKQ0KY2F0KCJUaW1lIGZvciB0cmFpbmluZyBtb2RlbD0iLCB0bV90cmFpblszXSwgInMgXG4iKSANCmNhdCgiVGltZSBmb3IgdGVzdGluZyBtb2RlbD0iLCB0bV90ZXN0WzNdLCAicyBcbiIpDQpgYGANCg0KIyMjUmVmZXJlbmNlDQotIER1LCBTLiwgVGFvLCBZLiwgJiBNYXJ0aW5leiwgQS4gTS4gKDIwMTQpLiBDb21wb3VuZCBmYWNpYWwgZXhwcmVzc2lvbnMgb2YgZW1vdGlvbi4gUHJvY2VlZGluZ3Mgb2YgdGhlIE5hdGlvbmFsIEFjYWRlbXkgb2YgU2NpZW5jZXMsIDExMSgxNSksIEUxNDU0LUUxNDYyLg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQo=